classdef ET_SACCADE < ET_GEN
    
    properties
        % Fixation
        fix = struct('w',0.25, ... Fixation width in degrees.
            'h',0.25, ... fixation width in degrees
            'color', [255 255 255], ... % Color of the target
            'shape','FillOval')
        % Target
        target = struct('w',0.25, ... Target width in degrees
            'h',0.25, ... Target width in degrees
            'color', [255 255 255], ... % Color of the target
            'ecc', 8, ... Degrees of eccentricity
            'shape', 'FillOval')
        % Configuration of the blocks
        bc = struct('bg',{[0,0,1],[0,0,1],[0,0,0],[0,0,0]},...
            'tc',[255,255,255],...
            'fc',[255,255,255],...
            'w',0.25, ... % width 
            'h',0.25) % height
        % Fixation window
        fix_win = [-30,30,30,-30] % left,up,right,down. Pixels
        % Behavioral results
        bh_r
        % How many trials before recalibration
        recal = 1e6
    end
        
    methods
        
        
        function prepareBlocks(self,blocks)
            % Randomize blocks
            %
            %   BLOCKS      A vectors of number coded blocks. All this
            %   blocks will be shown in randomized order.
            %
            
            self.blocks = blocks(randperm(numel(blocks)));
            self.num_blocks = numel(blocks);
        end        
        
        function prepareTrials(self,nt,js)
            % Prepares a matrix of the trials using information about the
            % number of the trials.
            %
            %   NT      Total number of trials displayed.
            %   JS      Jump size. It specifies the size of the target jump
            %           or displacement. It is conterbalance between left and
            %           right. Movements are etiher left or right.
            
            % Counter balance left and right hemifield and left and right
            % movement of target.
            nt = nt + numel(js)*2*2 - mod(nt,numel(js)*2*2);
            
            self.num_trials = nt;
            self.trials = zeros(self.num_trials,self.num_blocks);
            
            % Randomize whether left or right hemifield and left and right
            % movement.
            % left himfield left movement = 1
            % left hemifield right movement = 2
            % right hemifield left movement = 3
            % right hemifield right movement = 4
            for i = 1:self.num_blocks
                self.trials(:,i) = kron([js+1i js+2i js+3i js+4i], ...
                    ones(1,nt/(numel(js)*4)));
                self.trials(:,i) = self.trials(randperm(self.num_trials),i);
            end
            
            % Start a matrix for the behavioral results
            self.bh_r = nan(self.num_trials,self.num_blocks);
            
        end
        
        
        function runScreen(self)
            % Runs the screen.
            
            goout = 0; % Just in case
            
            % Start dialog
            self.startDialog()
                        
            % Goes to a high priority level
            % Priority(1) How to use this without making a massive
            % dissaster? What are the differences of using Priority in
            % different OS (windows and mac os?). 
            
            % Main loop going through blocks and trials
            self.startGM(1);
            for i = 1:self.num_blocks
                % Preamble
                startBackground_(self,i)
                b = self.blocks(i);
                % Calibrate once per block
                % Iterate over trials. If goout stops trials and blocks
                for j = 1:self.num_trials
                    if mod(j,self.recal) == 1 && self.EyeLink
                        fprintf(1,'%d,%d,%d\n',self.bc(b).bg);
                        calibrateEL(self.scn.p,self.scn.mon,0,...
                            self.bc(b).bg,self.bc(b).tc);
                    end
                    [~,goout] = self.expLoop_((self.num_trials*(i-1))+j);
                    if goout
                        break
                    end
                end
                
                % Abort block
                if goout
                    break
                end
                
                % Pause between blocks
                self.centeredText(sprintf(['Block %d finished.' ...
                    'Press ''q'' to quit.' ...
                    ' Press any other key to continue. '], i))
                KbEventFlush();
                % Buffer time
                WaitSecs(0.3);
                while 1
                    [avail,~,k] = KbCheck();
                    if avail
                        % If the key is q
                        if find(k) == KbName('q')
                            goout = 1;
                        end
                        break
                    end
                end
                % Exit
                if goout
                    break
                end
            end
            
            self.exit()
            
        end
        
        function startGM(self,block)
            % Initializes the graphical motor, i.e., cogent or PTB.
            
            self.el_pointer = EYELINK_EXP(self);
            
            if self.EyeLink
                Eyelink('Initialize');
                % If stipulated, configure manually the final.ini file
                if self.el_pointer.mc
                    self.cnfFinal()
                end
            end
            
            % Prevents input into matlabs
            if isunix()
                % Doesn work in windows
                ListenChar(2);
            end
            
            % Makes the keyboard insensible to capslock pressing
            
            [self.scn.p] = Screen('OpenWindow',0, self.scn.color,...
                []);
            Screen('TextSize',self.scn.p,40)
            Screen('FillRect',self.scn.p,self.bc(self.blocks(block)).bg);
            
            Screen(self.scn.p, 'Flip');
            if self.EyeLink
                HideCursor();
            end
            % Wait to have any input key pressed
            self.centeredText('Press a key when ready...');
            KbEventFlush();
            WaitSecs(0.3);
            while ~KbCheck();
            end            
            WaitSecs(0.3);
        end
        
        function sendParameters(self,trial)
            % Send information to the eyetracker which is worth to store.
            [b,t] = ind2sub([self.num_blocks self.num_trials],trial);
            Eyelink('Message',['BLOCK ' num2str(b)]);
            Eyelink('Message',['TRIAL ' num2str(t)]);
            Eyelink('Message',['SUBJECT ' num2str(self.exp_par.sbj)]);
            Eyelink('Message',['EXPERIMENT ' num2str(self.exp_par.exp)]);
        end
        
        function startBackground_(self,b)
            
            Screen('FillRect',self.scn.p,self.bc(self.blocks(b)).bg);
            Screen(self.scn.p, 'Flip');
        end
        
        function [ok,goout] = expLoop_(self,trial)
            % Runs the main experimental loop.
            %
            % TRIAL     Trial number.
            
            [b,t] = ind2sub([self.num_blocks self.num_trials],trial);
            
            b = self.blocks(b);
            
            % Initilize Eyelink
            if self.EyeLink
                % open a file on the eyetracker
                el = EyelinkInitDefaults(self.scn.p);
                Eyelink('Openfile', ...
                    ['s' num2str(self.exp_par.sbj) 'b' num2str(b) ...
                    't' num2str(t) '.edf']);
                Eyelink('StartRecording');
                self.sendParameters(trial);
            end
            
            
            switch imag(self.trials(trial))
                case 1
                    % hemifield
                    h = -1;
                    % displacement
                    dp = -1;
                case 2
                    h = -1;
                    dp = 1;
                case 3
                    h = 1;
                    dp = -1;
                case 4
                    h = 1;
                    dp = 1;
            end
            
            
            sp_ = @(cx,w,h) [ cx - w/2,self.scn.h/2 - h/2,cx ...
                + w/2,self.scn.h/2 + h/2];
            % degrees to pixels
            d2p_ = @(d) round(self.scn.dist*tand(d)*(self.scn.w/self.scn.wcm));
                                   
            fix_init = 0; % Time since fixation started
            phase = 0; % phase of experiment.
            target_on = 0; % Target has been displayed.
            target_moved = 0; % Target has been moveds.
            ok = 1; % Keep going or abort
            avail = 0; % Just in case.
            goout = 0; % Just in case.
            
            % Clean stack and have a short buffer
            KbEventFlush();
            WaitSecs(0.2);
            
            % Display fixation
            % =====================================================
            Screen('FillRect',self.scn.p,self.bc(b).bg);
            cx = self.scn.w/2;
            smp = sp_(cx,d2p_(self.fix.w),d2p_(self.fix.h));
            Screen(self.fix.shape,self.scn.p,tc(),round(smp));
            Screen( 'Flip',self.scn.p);
            fx = [ self.scn.w/2,self.scn.h/2];
            
            t0 = GetSecs();
            
            if isunix()
                Priority(1);
            elseif ispc()
                priorityLevel=MaxPriority(self.scn.p);
                Priority(priorityLevel);
            end
            
            while ok
                % If the subject presses any key during the trial, it is
                % aborted This only affects the two fist phases of the
                % experiment.
                avail = KbCheck();
                if avail
                    if self.EyeLink
                        Eyelink('MESSAGE','ABORT TRIAL');
                    end
                    ok = 0;
                    break
                end
                
                if ~self.EyeLink
                    [mx,my] = GetMouse();
                elseif self.EyeLink 
                    % get the sample in the form of an event structure
                    evt = Eyelink( 'NewestFloatSample');
                    % Get x and y from the eye selected
                    x = evt.gx(self.el_pointer.er+1);
                    y = evt.gy(self.el_pointer.er+1);
                    % do we have valid data and is the pupil visible?
                    if x~=el.MISSING_DATA && y~=el.MISSING_DATA 
                        mx=x;
                        my=y;
                    else
                        WaitSecs(0.010);
                        continue                    
                    end
                else
                    continue
                end
                
                % Wait until the eye has fixated for more than 0.2 seconds
                % do accep fixation. If accepted wait other 0.3 secs and
                % start trial.
                
                if phase == 0
                    if inwindow(fx,mx,my) && fix_init >= 0.2
                        if self.EyeLink
                            Eyelink('MESSAGE','PHASE 0');
                            Eyelink('MESSAGE','SACC_INIT');
                        end
                        phase = 1;
                    elseif inwindow(fx,mx,my) && 0 < fix_init && fix_init < 0.2
                        fix_init = GetSecs() - t0;
                    elseif ~inwindow(fx,mx,my) && 0 < fix_init && fix_init < 0.2
                        if self.EyeLink
                            Eyelink('MESSAGE','SACC_END');
                        end
                        fix_init = 0;
                    elseif inwindow(fx,mx,my) && 0 == fix_init
                        t0 = GetSecs();
                        % Adds a small quatity of time, just in case.
                        fix_init = 0.00001;
                    end
                    continue
                end
                
                if ~target_on
                    % Display target
                    % Assuming fovea is centered on the fixation
                    % =====================================================
                    cx = self.scn.w/2;
                    smp = sp_(cx,d2p_(self.fix.h),d2p_(self.fix.w));
                    Screen(self.fix.shape,self.scn.p,tc(),round(smp));
                    % Target
                    cx = h*d2p_(self.target.ecc) + self.scn.w/2;
                    smp = sp_(cx,d2p_(self.target.w),d2p_(self.target.h));
                    Screen(self.target.shape,self.scn.p,tc(),round(smp));
                    Screen('Flip',self.scn.p);
                    target_on = 1;
                end

                if phase == 1
                    if inwindow(fx,mx,my) && fix_init >= 0.5
                        if self.EyeLink
                            Eyelink('MESSAGE','PHASE 2');
                        end
                        phase = 2;
                    elseif inwindow(fx,mx,my) && fix_init < 0.5
                        fix_init = GetSecs() - t0;
                    elseif ~inwindow(fx,mx,my) && fix_init < 0.5
                        if self.EyeLink
                            Eyelink('MESSAGE','SACC_END');
                            Eyelink('MESSAGE','ABORT TRIAL');
                        end
                        % The subject failed to fixate while the targe was
                        % displayed. It aborts the trial and goes to the
                        % next.
                        ok = 0;
                    end
                    continue
                end

                if ~target_moved
                    % Target is moved by a certain factor
                    % =====================================================
                    
                    cx = self.scn.w/2;
                    smp = sp_(cx,d2p_(self.fix.w),d2p_(self.fix.h));
                    Screen(self.fix.shape,self.scn.p,tc(),round(smp));
                    
                    cx = h*d2p_(self.target.ecc + dp*real(self.trials(trial))) + self.scn.w/2;
                    smp = sp_(cx,d2p_(self.target.w),d2p_(self.target.h));
                    Screen(self.target.shape,self.scn.p,tc(),smp);
                    
                    Screen('Flip',self.scn.p);
                    target_moved = 1;
                end
                % Subject should fixate for 200ms

                if phase == 2
                    if inwindow(fx,mx,my) && fix_init && fix_init >= 0.7
                        phase = 3;
                    elseif inwindow(fx,mx,my) && fix_init < 0.7
                        fix_init = GetSecs() - t0;
                    elseif ~inwindow(fx,mx,my) && fix_init < 0.7
                        if self.EyeLink
                            Eyelink('MESSAGE','SACC_END');
                            Eyelink('MESSAGE','ABORT TRIAL');
                        end
                        
                        % The subject failed to fixate while the targe was
                        % displayed. It aborts the trial and goes to the
                        % next.
                        ok = 0;
                    end
                    continue
                end
                                
                if phase == 3
                    % Every thing went well.
                    if self.EyeLink
                        Eyelink('MESSAGE','SACC_END');
                        Eyelink('MESSAGE','TRIAL ACEPTED');
                    end
                    break
                end
            end
            % Collect behavioral response and send it to the EDF file.
            KbEventFlush();

            if ok
                % Clean up screen and wait for behavioral response
                Screen(self.scn.p, 'Flip');
                t = GetSecs();
                while 2 > GetSecs() - t;
                    [avail,~,k] = KbCheck();
                    % Take only the firs response
                    if avail
                        self.bh_r(trial) = find(k);
                        fprintf(1,'Subject pressed %d\n.',find(k));
                        break
                    end
                end
                while 2 > GetSecs() - t
                end
                    
                % Send message to the EyeLink
                if self.EyeLink
                    if avail
                        Eyelink('MESSAGE',sprintf('KEY %d',find(k)));
                    else
                        Eyelink('MESSAGE','KEY nan');
                    end
                end
            else
                fprintf('Block %d, trial %d, aborted.\n',...
                    floor(trial/self.num_trials)+1, ...
                    trial - floor(trial/self.num_trials)*self.num_trials);
            end
            % Stop the EyeLink
            if self.EyeLink
                Eyelink('Closefile');
                Eyelink('ReceiveFile');
            end
            
            if ~ok
                self.centeredText('A',tc());
                WaitSecs(1);
            end
            
            % Intertrial 
            % =============================================================
            % Display a intertrial stimuli
            KbEventFlush();
            t = GetSecs();
            
            % Display a cross in the screen during the intertrail interval.
            self.centeredText('+',tc(),20);
                
            while 1 > GetSecs() - t
                % Pause Trial if key is pressed
                [avail,~,k] = KbCheck();
                if avail
                    KbEventFlush();
                    self.centeredText(['Press ''q'' to quit.' ...
                        ' Press any other key to continue. '])
                    % Buffer.
                    WaitSecs(0.5);
                    while 1
                        [avail,~,k] = KbCheck();
                        if avail; 
                            break
                        end
                    end
                    % If the key is q
                    if find(k) == KbName('q')
                        goout = 1;
                        break
                    end
                    KbEventFlush();
                    break
                end
            end
            
            
            function fix = inwindow(cx,mx,my,fix_win)
                % determine if gx and gy are within fixation window
                if nargin < 4
                    fix_win = self.fix_win;
                end 
                fix = all([mx > (cx(1) + fix_win(1))
                    my < (cx(2) + fix_win(2))
                    mx < (cx(1) + fix_win(3))
                    my > (cx(2) + fix_win(4))
                ]);
            end
            
            
            function c = tc() 
                % target color
                c = self.bc(b).tc;
            end
            
        end
        
        
        
    end
    
    
end
